package com.gitee.wsl.io.channel

import com.gitee.wsl.io.buildPacket
import com.gitee.wsl.io.channel.read.ByteReadChannel
import com.gitee.wsl.io.channel.read.ext.readByte
import com.gitee.wsl.io.channel.read.ext.readRemaining
import com.gitee.wsl.io.channel.read.ext.toByteArray
import com.gitee.wsl.io.channel.write.ext.writeByte
import com.gitee.wsl.io.channel.write.ext.writeFully
import com.gitee.wsl.io.channel.write.ext.writePacket
import com.gitee.wsl.io.readText
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.yield
import kotlinx.io.EOFException
import kotlinx.io.IOException
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import kotlin.time.Duration.Companion.milliseconds


/*
 * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
 */

class ByteChannelTest {

    @Test
    fun testReadFromEmpty() = runTest {
        val channel = ByteChannel()
        channel.flushAndClose()

        assertFailsWith<EOFException> {
            channel.readByte()
        }
    }

    @Test
    fun testWriteReadByte() = runTest {
        val channel = ByteChannel()
        channel.writeByte(42)
        channel.flushAndClose()
        assertEquals(42, channel.readByte())
    }

    @Test
    fun testCancel() = runTest {
        val channel = ByteChannel()
        channel.cancel()

        assertFailsWith<IOException> {
            channel.readByte()
        }
    }

    @Test
    fun testWriteInClosedChannel() = runTest {
        val channel = ByteChannel()
        channel.flushAndClose()

        assertTrue(channel.isClosedForWrite)
        assertFailsWith<IOException> {
            channel.writeByte(42)
        }
    }

    @Test
    fun testCreateFromArray() = runTest {
        val array = byteArrayOf(1, 2, 3, 4, 5)
        val channel = ByteReadChannel(array)
        val result = channel.toByteArray()
        assertTrue(array.contentEquals(result))
    }

    @Test
    fun testChannelFromString() = runTest {
        val string = "Hello, world!"
        val channel = ByteReadChannel(string)
        val result = channel.readRemaining().readText()
        assertEquals(string, result)
    }

    @Test
    fun testCancelByteReadChannel() = runTest {
        val channel = ByteReadChannel(byteArrayOf(1, 2, 3, 4, 5))
        channel.cancel()
        assertFailsWith<IOException> {
            channel.readByte()
        }
    }

    @Test
    fun testCloseAfterAwait() = runTest {
        val channel = ByteChannel()
        val job = launch(start = CoroutineStart.UNDISPATCHED) {
            channel.awaitContent()
        }

        channel.flushAndClose()
        job.join()
    }

    @Test
    fun testChannelMaxSize() = runTest(timeout = 1000.milliseconds) {
        val channel = ByteChannel()
        val job = launch(Dispatchers.Unconfined) {
            channel.writeFully(ByteArray(CHANNEL_MAX_SIZE))
        }

        delay(100)
        assertFalse(job.isCompleted)

        channel.readByte()
        job.join()
    }

    @Test
    fun testChannelMaxSizeWithException() = runTest {
        val channel = ByteChannel()
        var writerThrows = false
        val deferred = async(Dispatchers.Unconfined) {
            try {
                channel.writeFully(ByteArray(CHANNEL_MAX_SIZE))
            } catch (cause: IOException) {
                writerThrows = true
            }
        }

        assertFalse(deferred.isCompleted)

        channel.cancel()
        deferred.await()

        assertTrue(writerThrows)
    }

    @Test
    fun testIsCloseForReadAfterCancel() = runTest {
        val packet = buildPacket {
            writeInt(1)
            writeInt(2)
            writeInt(3)
        }

        val channel = ByteChannel()
        channel.writePacket(packet)
        channel.flush()
        channel.cancel()

        assertTrue(channel.isClosedForRead)
    }

    @Test
    fun testWriteAndFlushResumeReader() = runTest {
        val channel = ByteChannel()
        val reader = async {
            channel.readByte()
        }

        yield()

        channel.writeByte(42)
        channel.flush()

        assertEquals(42, reader.await())
    }
}
